// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using SnapObjects.Commons;

namespace SnapObjects.Data.DB2
{
    internal class DB2UpdateSqlGenerator : UpdateSqlGenerator
    {
        public DB2UpdateSqlGenerator(DB2SqlGenerationHelper sqlServerSqlGenerationHelper) :
            base(sqlServerSqlGenerationHelper)
        {
        }

        public override ResultSetMapping AppendRawUpdateSql(
            [NotNull] StringBuilder sqlStringBuilder, [NotNull] string rawUpdateSql, int sqlPosition)
        {
            var tmpSqlBuilder = new StringBuilder();

            tmpSqlBuilder.Append(rawUpdateSql);

            var resultSetMapping = ResultSetMapping.NoResultSet;

            if (rawUpdateSql.Trim().StartsWith("DELETE", StringComparison.OrdinalIgnoreCase))
            {
                resultSetMapping = this.AppendDeleteAffectedCountSql(tmpSqlBuilder);
            }
            else if (rawUpdateSql.Trim().StartsWith("INSERT", StringComparison.OrdinalIgnoreCase) ||
                rawUpdateSql.Trim().StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase))
            {
                resultSetMapping = this.AppendSelectAffectedCountSql(tmpSqlBuilder, null, null, 0);
            }

            sqlStringBuilder.Append(tmpSqlBuilder);

            return resultSetMapping;
        }

        public override ResultSetMapping AppendInsertSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            var tmpSqlBuilder = new StringBuilder();

            var resultSetMapping = base.AppendInsertSql(tmpSqlBuilder, modelUpdateInfo, sqlPosition);

            sqlStringBuilder.Append(tmpSqlBuilder);

            return resultSetMapping;
        }

        public override ResultSetMapping AppendUpdateSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            var tmpSqlBuilder = new StringBuilder();

            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var operations = modelUpdateInfo.ColumnModifications;

            var writeOperations = operations.Where(o => o.IsWrite).ToList();

            if (writeOperations.Count > 0)
            {
                var conditionOperations = operations.Where(o => o.IsCondition).ToList();

                this.AppendUpdateSql(tmpSqlBuilder, name, schema, writeOperations, conditionOperations);

                var readOperations = operations.Where(o => o.IsRead).ToList();

                if (readOperations.Count > 0)
                {
                    this.AppendSelectAffectedCountSql(tmpSqlBuilder, name, schema, sqlPosition);

                    var keyOperations = operations.Where(o => o.IsKey).ToList();

                    var resultSetMapp = this.AppendUpdateSelectAffectedSql(
                        tmpSqlBuilder, name, schema, readOperations, keyOperations, sqlPosition);

                    sqlStringBuilder.Append(tmpSqlBuilder);

                    return resultSetMapp;
                }
            }

            var resultSetMapping = this.AppendSelectAffectedCountSql(tmpSqlBuilder, name, schema, sqlPosition);

            sqlStringBuilder.Append(tmpSqlBuilder);

            return resultSetMapping;
        }

        public override ResultSetMapping AppendDeleteSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            var tmpSqlBuilder = new StringBuilder();

            Check.NotNull(tmpSqlBuilder, nameof(tmpSqlBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var conditionOperations = modelUpdateInfo.ColumnModifications.Where(o => o.IsCondition).ToList();

            this.AppendDeleteSql(tmpSqlBuilder, name, schema, conditionOperations);

            var resultSetMapping = this.AppendDeleteAffectedCountSql(tmpSqlBuilder);

            sqlStringBuilder.Append(tmpSqlBuilder);

            return resultSetMapping;
        }

        protected override void AppendDeleteSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(conditionOperations, nameof(conditionOperations));

            this.AppendDeleteHeader(sqlStringBuilder, name, schema);
            this.AppendWhereClause(sqlStringBuilder, conditionOperations);
        }

        protected override void AppendUpdateSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> writeOperations,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotEmpty(writeOperations, nameof(writeOperations));
            Check.NotNull(conditionOperations, nameof(conditionOperations));

            this.AppendUpdateHeader(sqlStringBuilder, name, schema, writeOperations);
            this.AppendWhereClause(sqlStringBuilder, conditionOperations);
        }

        protected override ResultSetMapping AppendSelectAffectedSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> readOperations,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(readOperations, nameof(readOperations));
            Check.NotNull(conditionOperations, nameof(conditionOperations));

            var tmpSqlStrBuilder = new StringBuilder();

            this.AppendSelectHeader(tmpSqlStrBuilder, readOperations);

            tmpSqlStrBuilder.Append(" from final table(");
            sqlStringBuilder.Insert(0, tmpSqlStrBuilder);
            sqlStringBuilder.Append(")");

            sqlStringBuilder.Append(this.SqlGenerationHelper.StatementTerminator).AppendLine()
                .AppendLine();

            return ResultSetMapping.SelectAndLastInResultSet;
        }

        protected override void AppendInsertSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> writeOperations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(writeOperations, nameof(writeOperations));

            this.AppendInsertHeader(sqlStringBuilder, name, schema, writeOperations);
            this.AppendValuesHeader(sqlStringBuilder, writeOperations);
            this.AppendValues(sqlStringBuilder, writeOperations);

            sqlStringBuilder.AppendLine();
        }

        protected override ResultSetMapping AppendSelectAffectedCountSql(
            StringBuilder sqlStringBuilder, string name, string schema, int sqlPosition)
        {
            sqlStringBuilder.Insert(0, "SELECT COUNT(1) FROM final table(")
                .Append(")")
                .Append(this.SqlGenerationHelper.StatementTerminator)
                .AppendLine();

            return ResultSetMapping.RowCountAndLastInResultSet;
        }

        protected override void AppendIdentityWhereCondition(StringBuilder sqlStringBuilder, ColumnModification columnModification)
        {
            //DO Nothing
        }

        protected override void AppendRowsAffectedWhereCondition(StringBuilder sqlStringBuilder, int expectedRowsAffected)
        {
            //DO Nothing
        }

        private ResultSetMapping AppendUpdateSelectAffectedSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> readOperations,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(readOperations, nameof(readOperations));
            Check.NotNull(conditionOperations, nameof(conditionOperations));

            this.AppendSelectHeader(sqlStringBuilder, readOperations);
            this.AppendFromClause(sqlStringBuilder, name, schema);

            this.AppendUpdateSelectWhereAffectedClause(sqlStringBuilder, conditionOperations);

            sqlStringBuilder.Append(this.SqlGenerationHelper.StatementTerminator).AppendLine();

            return ResultSetMapping.RowCountAndNotLastInResultSet;
        }

        private void AppendUpdateSelectWhereAffectedClause(
            [NotNull] StringBuilder sqlStringBuilder, [NotNull] IReadOnlyList<ColumnModification> operations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(operations, nameof(operations));

            sqlStringBuilder.AppendLine().Append("WHERE ");

            if (operations.Count > 0)
            {
                sqlStringBuilder.AppendValues(operations, (sb, v) => this.AppendWhereCondition(sb, v), " AND ");
            }
        }

        private void AppendWhereCondition(StringBuilder sb, ColumnModification columnModification)
        {
            if (columnModification.IsKey || columnModification.IsIdentity)
            {
                if (columnModification.IsRead)
                {
                    this.AppendIdentityWhereCondition(sb, columnModification);
                }
                else
                {
                    this.AppendWhereAffectedCondition(sb, columnModification, columnModification.IsWrite ? false : true);
                }
            }
        }

        protected ResultSetMapping AppendDeleteAffectedCountSql(StringBuilder sqlStringBuilder)
        {
            sqlStringBuilder.Insert(0, "SELECT COUNT(1) FROM old table(")
                .Append(")")
                .Append(this.SqlGenerationHelper.StatementTerminator)
                .AppendLine();

            return ResultSetMapping.RowCountAndLastInResultSet;
        }
    }
}
